#ifndef B2D_OBJECT_H
#define B2D_OBJECT_H

#include "Prerequisites.h"
#include <type_traits>

NS_B2D_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Generates a unique id for each type. Each specialisation of the structure will create
/// 		its own variable "mId". The function Id() returns the memory address of this variable
/// 		and thus ensuring the value is different for every type as it will be the memory
/// 		address of a distinct variable.
///
/// \tparam	T	Type.
////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename T>
struct B2D_API TypeToId final
{
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Gets the id for this type.
	///
	/// \return	.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	static size_t Id(void)
	{
		return reinterpret_cast<size_t>(&mId);
	}
private:
	const static size_t mId = 0;
};

class Object;

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Represents a type of object. It holds a pointer to its base class (unless the type is
/// 		Object, in which case this is null), and a unique hash for the type. ObjectType can
/// 		be copied and assigned, the default constructor simple sets it to the type of the
/// 		Object base class.
////////////////////////////////////////////////////////////////////////////////////////////////////
class B2D_API ObjectType final
{
private:
	ObjectType* mBaseType;
	size_t mTypeId;

public:
	ObjectType(void);
	ObjectType(const ObjectType& t);
	~ObjectType(void) { }

	ObjectType& operator = (const ObjectType& t);

	// Compare functions
	bool operator <  (const ObjectType& t) const { return mTypeId <  t.mTypeId; }
	bool operator <= (const ObjectType& t) const { return mTypeId <= t.mTypeId; }
	bool operator == (const ObjectType& t) const { return mTypeId == t.mTypeId; }
	bool operator != (const ObjectType& t) const { return mTypeId != t.mTypeId; }
	bool operator >= (const ObjectType& t) const { return mTypeId >= t.mTypeId; }
	bool operator >  (const ObjectType& t) const { return mTypeId >  t.mTypeId; }
	
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Creates an ObjectType for type T which derives from type TBase.
	///
	/// \tparam	typename T	  	Type of the object.
	/// \tparam	typename TBase	Type of the base class of the object.
	///
	/// \return	A new ObjectType class.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename T, typename TBase>
	static ObjectType create(void)
	{
		static_assert(std::is_base_of<Object,T>::value,
			"Can't create ObjectType for a class that doesnt derive from Object.");
		static_assert(std::is_base_of<Object,TBase>::value,
			"Can't derive from non-Object type.");

		ObjectType t;
		t.mBaseType = const_cast<ObjectType*>(&TBase::Type);
		t.mTypeId = TypeToId<T>::Id();
		return t;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Query if this type is exactly the same as that of the provided type.
	///
	/// \param	t	The type to compare.
	///
	/// \return	true matches type, false if not.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	bool isOfType(const ObjectType& t) const;
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Check if this type derives from the provided type.
	///
	/// \param	t	The type to check for derivation.
	///
	/// \return	true if it derives from it, false otherwise.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	bool derivesFromType(const ObjectType& t) const;
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Gets the unique id for this ObjectType.
	///
	/// \return	The identifier.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	size_t getId(void) const { return mTypeId; }
};

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Base class for all objects. although not compulsory, for proper type identification,
/// 		every class deriving from object must define B2D_OBJECT in the header and
/// 		B2D_OBJECT_IMPL in the implementation file. This will allow the Object functions and
/// 		object_dynamic_cast to recognise the derived type.
////////////////////////////////////////////////////////////////////////////////////////////////////
class B2D_API Object
{
	B2D_INTERFACE(Object)

public:
	const static ObjectType Type;

public:
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Gets the type of object.
	///
	/// \return	The type.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	virtual const ObjectType& getType(void) const { return Type; }

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Gets unique object type identifier.
	///
	/// \return	The type unique id.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	FORCE_INLINE size_t getTypeId(void) const { return getType().getId(); }
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Query if this object is of the provided type.
	///
	/// \param	t	The type to check.
	///
	/// \return	true if it is of the provided type, false if not.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	FORCE_INLINE bool isOfType(const ObjectType& t) const { return getType().isOfType(t); }
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \brief	Query if this object derives from the given type.
	///
	/// \param	t	The type to check.
	///
	/// \return	true if it derives from it, false if it doesnt.
	////////////////////////////////////////////////////////////////////////////////////////////////////
	FORCE_INLINE bool derivesFromType(const ObjectType& t) const { return getType().derivesFromType(t); }
};

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	Object dynamic cast utility function.
///
/// \tparam	typename T	Type to cast to.
/// \param [in]	obj	The object to cast.
///
/// \return	null if it fails, else the cast result.
////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename T>
T* object_dynamic_cast(Object* obj)
{
	static_assert(std::is_base_of<Object,T>::value,
			"Can't use object_dynamic_cast for non-Object types.");
	if(obj == nullptr || !obj->derivesFromType(T::Type)) return nullptr;
	return static_cast<T*>(obj);
}

#define B2D_OBJECT \
public: \
	const static ObjectType Type; \
	virtual const ObjectType& getType(void) const { return Type; }

#define B2D_OBJECT_IMPL(type, base) \
	const ObjectType type::Type = ObjectType::create<type,base>();

NS_B2D_END

namespace std
{
	template<> struct hash<::b2d::ObjectType>
	{
		size_t operator()(const ::b2d::ObjectType& t)
		{
			return t.getId();
		}
	};
}

#endif
